﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using RPGProject.Data_Structures;


namespace RPGProject.IOBuffers
{
    /// <summary>
    /// A class that manages multiple OutputBuffers, and renders them to portions of the screen.  Also prevents overlap.
    /// </summary>
    class Screen
    {

        private readonly int width;
        private readonly int height;
        /// <summary>
        /// A list containing (x, y, OutputBuffer) that are located in this screen.
        /// </summary>
        private Vector<Tuple<int, int, OutputBuffer>> screenAllocations;
        /// <summary>
        /// Constructs a Screen that is width by height.
        /// </summary>
        /// <param name="width">The screen width.</param>
        /// <param name="height">The screen height.</param>
        public Screen(int width, int height)
        {
            this.width = width;
            this.height = height;
            this.screenAllocations = new Vector<Tuple<int, int, OutputBuffer>>();
        }
        /// <summary>
        /// Allocates a buffer to the screen, at (x, y).
        /// </summary>
        /// <param name="x">The x location of the buffer.</param>
        /// <param name="y">The y location of the buffer.</param>
        /// <param name="outBuffer">The buffer to allocate.</param>
        /// <returns>Whether the allocate failed or not.</returns>
        public bool AllocateScreen(int x, int y, OutputBuffer outBuffer)
        {
            // makes sure the buffer is within the screen.
            if (x < 0 || y < 0 || x + outBuffer.width > this.width || y + outBuffer.height > this.height)
                return false;
            else
            {
                // special case for no allocations
                if (screenAllocations.Count == 0)
                {
                    this.screenAllocations.Add(Tuple.Create(x, y, outBuffer));
                    return true;
                }
                else
                {
                    // using Seperating Axis Theorem to check if the new buffer would overlap with
                    // any buffers already on the screen.  Link below details the algorithm more.
                    // http://www.metanetsoftware.com/technique/tutorialA.html
                    double x1Halflen = outBuffer.width / 2.0;
                    double y1Halflen = outBuffer.height / 2.0;
                    double x1Cen = x + x1Halflen;
                    double y1Cen = y + y1Halflen;
                    double x2Halflen;
                    double y2Halflen;
                    double x2Cen;
                    double y2Cen;
                    foreach (Tuple<int, int, OutputBuffer> testAgainst in this.screenAllocations)
                    {
                        x2Halflen = testAgainst.Item3.width / 2.0;
                        y2Halflen = testAgainst.Item3.height / 2.0;
                        x2Cen = testAgainst.Item1 + x2Halflen;
                        y2Cen = testAgainst.Item2 + y2Halflen;
                        // test the distance between the centers versus the total of the two half lengths
                        bool overlapX = Math.Abs(x1Cen - x2Cen) < x1Halflen + x2Halflen;
                        bool overlapY = Math.Abs(y1Cen - y2Cen) < y1Halflen + y2Halflen;
                        // if they overlap on bothe axes then fail the allocation
                        // that means that it would overlap with another buffer.
                        if (overlapX && overlapY)
                            return false;
                    }
                    // if we get here, there are no overlaps, so create it and return true
                    this.screenAllocations.Add(Tuple.Create(x, y, outBuffer));
                    return true;
                }
            }
        }
        /// <summary>
        /// Renders all the buffers.
        /// </summary>
        /// <param name="forceRender">Forces the buffers to rerender.</param>
        public void Render(bool forceRender = false)
        {
            Console.WindowWidth = this.width;
            Console.WindowHeight = this.height + 1;
            foreach (Tuple<int, int, OutputBuffer> testAgainst in this.screenAllocations)
            {
                testAgainst.Item3.RenderTo(testAgainst.Item1, testAgainst.Item2, false, forceRender);
            }
        }
        /// <summary>
        /// Return the screen allocations.
        /// </summary>
        public Vector<Tuple<int, int, OutputBuffer>> Buffers
        {
            get { return this.screenAllocations; }
        }
        /// <summary>
        /// Return the width of the screen.
        /// </summary>
        public int Width 
        {
            get { return this.width; }  
        }
        /// <summary>
        /// Return the height of the screen.
        /// </summary>
        public int Height 
        {
            get { return this.height; }
        }
    }
}
